package cloudone.client.internal; import cloudone.internal.ApplicationFullName; import cloudone.internal.dto.PortInfo; import cloudone.internal.nimbostratus.CumulonimbusClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.xml.ws.WebServiceException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Veri primitive demo load balancer for application ports based on reaction time. * * @author Martin Mares (martin.mares at oracle.com) */ // This implementation is allways updating from Cumulonimbus. It should be implemented in much more caching version. public class LoadBalancer { private static class Stats { public final int port; public long durationSum; public Stats(int port, long durationSum) { this.durationSum = durationSum; this.port = port; } } private static class Ports { public volatile long updated = -1; public Map<Integer, Stats> ports = new HashMap<>(); } private static final Logger LOGGER = LoggerFactory.getLogger(LoadBalancer.class); private static final LoadBalancer INSTANCE = new LoadBalancer(); private static long UPDATE_DURATION = 4000L; private final ConcurrentMap<ApplicationFullName, Ports> registry = new ConcurrentHashMap<>(); private LoadBalancer() { } private void update(ApplicationFullName appName, Ports ports) { if (appName == null) { return; } try { HashMap<Integer, PortInfo> servicePorts = CumulonimbusClient.getInstance().getServicePorts(appName.getServiceName()); final List<Integer> registeredPorts = new ArrayList<>(servicePorts.size()); for (PortInfo portInfo : servicePorts.values()) { Integer port = portInfo.getApplicationPorts().get(appName.getApplicationName()); if (port != null) { registeredPorts.add(port); } } //Check for added ports final List<Integer> addPorts = new ArrayList<>(); final Set<Integer> checkedPorts = new HashSet<>(ports.ports.size()); for (Integer port : registeredPorts) { boolean add = true; for (Integer key : ports.ports.keySet()) { if (key.equals(port)) { add = false; checkedPorts.add(port); break; } } if (add) { addPorts.add(port); } } //Check for removed ports if (checkedPorts.size() < ports.ports.size()) { Iterator<Map.Entry<Integer, Stats>> iterator = ports.ports.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<Integer, Stats> entry = iterator.next(); if (!checkedPorts.contains(entry.getKey())) { iterator.remove(); } } } //Add added ports if (!addPorts.isEmpty()) { long baseDuration = 0L; Stats lowest = getLowest(ports); if (lowest != null) { baseDuration = lowest.durationSum; } for (Integer addPort : addPorts) { ports.ports.put(addPort, new Stats(addPort, baseDuration)); } } //Update timestamp ports.updated = System.currentTimeMillis(); } catch (WebServiceException webExc) { LOGGER.warn("No more registered instance of service: " + appName.getServiceName() + "! Remove from cache"); ports.ports.clear(); ports.updated = System.currentTimeMillis(); } catch (Exception e) { LOGGER.error("Cannot update ports for " + appName + "!", e); } } private Stats getLowest(Ports ports) { synchronized (ports) { Stats result = null; for (Stats stats : ports.ports.values()) { if (result == null || result.durationSum > stats.durationSum) { result = stats; } } return result; } } public Ports getPorts(ApplicationFullName name) { Ports ports = registry.computeIfAbsent(name, nm -> new Ports()); if ((ports.updated + UPDATE_DURATION) < System.currentTimeMillis()) { synchronized (ports) { if ((ports.updated + UPDATE_DURATION) < System.currentTimeMillis()) { update(name, ports); } } } return ports; } /** * Finds best candidate for call. Returns negative value id not found. */ public int getPort(ApplicationFullName name) { Ports ports = getPorts(name); if (ports != null) { synchronized (ports) { Stats result = getLowest(ports); if (result != null) { result.durationSum++; return result.port; } } } return -1; } public void updateStats(ApplicationFullName name, int port, long duration) { if (name == null || port < 0 || duration < 0) { return; } Ports ports = registry.get(name); if (ports != null) { synchronized (ports) { Stats stats = ports.ports.get(port); if (stats != null) { try { stats.durationSum = Math.addExact(stats.durationSum, duration); } catch (ArithmeticException exc) { LOGGER.warn("Too long duration. Must compact"); for (Stats stats1 : ports.ports.values()) { stats1.durationSum -= stats.durationSum; if (stats1.durationSum < 0) { stats1.durationSum = 0; } } stats.durationSum = duration; } } } } } public static LoadBalancer getInstance() { return INSTANCE; } }